//
// parms_xml.cpp
// libprotea
// Tools Library for the Protea Project / Configuration XML Handling
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include "../AppHandler/AppInterface.h"
#include "../OSDep/utf8.h"
#include "../OSDep/files.h"
#include "../OSDep/dir.h"
#include "../OSDep/registry.h"
#include "../xmlhelper.h"
#include "parms_xml.h"
#include <stdlib.h>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/SAXParseException.hpp>




// GCS Initialization Standard
//////////////////////////////////////////////////////////////////////////////
const char lt[] = "<";
const char sl[] = "/";
const char gt[] = ">";

const char xmltag[] =			"<?xml version=\"1.0\"?>";
const char proteatag[] =			"Protea";
const char	versiontag[] =			"Version";
const char	gcstag[] =				"GCS";
const char	porttag[] =				"Port";
const char	machinetag[] =			"Machine";
const char	threadstag[] =			"Threads";
const char	authenticationtag[] =		"Authentication";
const char	methodtag[] =				"Method";
const char	servertag[] =				"Server";
const char	servicetag[] =				"Service";
const char	domaintag[] =				"Domain";
const char	settingstag[] =			"Settings";
const char	applisttag[] =			"AppList";
const char		apptag[] =				"App";
const char		numbertag[] =			"Number";
const char		nametag[] =				"Name";
//const char		machinetag[] =			"Machine";
const char		nettag[] =				"NET";
const char		debugtag[] =				"Debug";
const char		typetag[] =					"Type";
const char		leveltag[] =				"Level";
const char	interfacelisttag[] =	"InterfaceList";
const char		interfacetag[] =		"Interface";
//const char		nametag[] =				"Name";
//const char		machinetag[] =			"Machine";
//const char		porttag[] =				"Port";
const char			tracingtag[] =			"Tracing";
//const char			threadstag[] =		"Threads";



// 
// Parsing functions for gcs.xml
//
//////////////////////////////////////////////////////////////////////////////
void parms_reader::CheckDebugTags( XERCES_CPP_NAMESPACE::DOMNode* debug, XCDebugInfo* currapp ) {
	currapp->admin_email = NULL;
	currapp->printer = NULL;
	currapp->smtp_host = NULL;
	currapp->fop_location = NULL;

	// Check for Debug tags
	while (debug) {
		UniTranslator tname( debug->getNodeName() );

		if (debug->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			if ( strcmp( tname, debugtag )==0) {
				char* type = NULL;
				char* level = NULL;

				XERCES_CPP_NAMESPACE::DOMNamedNodeMap* debuginfo = debug->getAttributes();
				for (unsigned int i=0; i<debuginfo->getLength(); i++) {
					XERCES_CPP_NAMESPACE::DOMNode* anode = debuginfo->item(i);
					UniTranslator name( anode->getNodeName() );
					UniTranslator value( anode->getNodeValue() );

					if (strcmp( name, typetag )==0) {
						type = strdup(value);
					} else if (strcmp( name, leveltag )==0) {
						level = strdup(value);
					} else 
						printf( "BAD XML!\n" );
				};

				if (type) {
					int level_number = TLV_LOW;

					// Determine the level
					if (strcmp( level, "Everything" )==0) {
						level_number = TLV_EVERYTHING;
					} else if (strcmp( level, "High" )==0) {
						level_number = TLV_HIGH;
					} else if (strcmp( level, "Medium" )==0) {
						level_number = TLV_MEDIUM;
					} else if (strcmp( level, "Low" )==0) {
						level_number = TLV_LOW;
					} else
						printf( "Invalid level\n" );

					free( level );

					// Determine the type
					if (strcmp( type, "Database" )==0) {
						currapp->debug_type_filter |= TRC_DATABASE;
						currapp->debug_level_cutoff[ TCL_DATABASE ] = level_number;
					} else if (strcmp( type, "Buffer" )==0) {
						currapp->debug_type_filter |= TRC_BUFFER | TRC_NOLABEL;
						currapp->debug_level_cutoff[ TCL_BUFFER ] = level_number;
						currapp->debug_level_cutoff[ TCL_NOLABEL ] = TLV_HIGH;
					} else if (strcmp( type, "Functions" )==0) {
						currapp->debug_type_filter |= TRC_FUNCTIONS;
						currapp->debug_level_cutoff[ TCL_FUNCTIONS ] = level_number;
					} else if (strcmp( type, "AppInterface" )==0) {
						currapp->debug_type_filter |= TRC_APPINTERFACE;
						currapp->debug_level_cutoff[ TCL_APPINTERFACE ] = level_number;
					} else if (strcmp( type, "Fields" )==0) {
						currapp->debug_type_filter |= TRC_FIELD;
						currapp->debug_level_cutoff[ TCL_FIELD ] = level_number;
					} else if (strcmp( type, "Messaging" )==0) {
						currapp->debug_type_filter |= TRC_MESSAGING;
						currapp->debug_level_cutoff[ TCL_MESSAGING ] = level_number;
						currapp->debug_type_filter |= TRC_TRAFFIC;
						currapp->debug_level_cutoff[ TCL_TRAFFIC ] = level_number;
					} else
						printf( "Invalid type!\n" );

					free( type );
				} else
					printf( "Expected type\n" );
			} else if (strcmp( tname, authenticationtag )==0) {
				HandleAuthentication( debug );
			} else if (strcmp( tname, settingstag )==0) {
				HandleSettings( debug->getFirstChild(), currapp );
			} else
				printf( "Expected debug tag\n" );
		};

		debug = debug->getNextSibling();
	}; // while debug
};



void parms_reader::HandleAppList( XERCES_CPP_NAMESPACE::DOMNode* applistnode ) {
	if (!want_apps)
		return;

	while (applistnode) {
		if (applistnode->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator alname( applistnode->getNodeName() );
			if (strcmp( alname, apptag )==0) {
				XCStartApp* currapp;

				currapp = new XCStartApp;
				currapp->next = applist;
				applist = currapp;

				XERCES_CPP_NAMESPACE::DOMNamedNodeMap* attrib = applistnode->getAttributes();
				for (unsigned int i=0; i<attrib->getLength(); i++) {
					XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
					UniTranslator name( anode->getNodeName() );
					UniTranslator value( anode->getNodeValue() );

					if ( strcmp( name, numbertag )==0 )
						currapp->number = atol( value );
					else if ( strcmp( name, nametag )==0 )
						currapp->name = strdup( value );
					else if ( strcmp( name, machinetag )==0 )
						currapp->machine = strdup( value );
					else if ( strcmp( name, nettag )==0 )
						currapp->NETapp = (bool)atol( value );
					else
						printf( "Invalid XML attrib\n" );
				};

				CheckDebugTags( applistnode->getFirstChild(), currapp );
			} else
				printf( "Invalid XML format\n" );
		};
		applistnode = applistnode->getNextSibling();
	};
};




void parms_reader::HandleInterfaceList( XERCES_CPP_NAMESPACE::DOMNode* intlistnode ) {
	if (!want_interfaces)
		return;

	while (intlistnode) {
		UniTranslator name( intlistnode->getNodeName() );

		if (strcmp( name, interfacetag )==0) {
			XCStartInterface* currint = new XCStartInterface();
			currint->tracing = 1;
			currint->worker_threads = 1;
			currint->next = interfacelist;
			interfacelist = currint;

			XERCES_CPP_NAMESPACE::DOMNamedNodeMap* attrib = intlistnode->getAttributes();
			for (unsigned int i=0; i<attrib->getLength(); i++) {
				XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
				UniTranslator name( anode->getNodeName() );
				UniTranslator value( anode->getNodeValue() );

				if ( strcmp( name, nametag )==0 )
					currint->name = strdup( value );
				else if ( !strcmp( name, machinetag ) )
					currint->machine = strdup( value );
				else if ( !strcmp( name, porttag ) )
					currint->port = atol( value );
				else if ( !strcmp( name, tracingtag ) )
					currint->tracing = atoi( value );
				else if ( !strcmp( name, threadstag ) )
					currint->worker_threads = atoi( value );
				else
					printf( "Invalid XML attrib\n" );
			};
		};

		intlistnode = intlistnode->getNextSibling();
	};
};





void parms_reader::HandleAuthentication( XERCES_CPP_NAMESPACE::DOMNode* exnode ) {
	// Check the attributes of the tag for the method
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* attrib = exnode->getAttributes();
	for (unsigned int i=0; i<attrib->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
		if (anode->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ATTRIBUTE_NODE) {
			UniTranslator name( anode->getNodeName() );
			UniTranslator value( anode->getNodeValue() );

			// Determine the authorization method
			if (strcmp( name, methodtag )==0) {
				if (strcmp( value, "PAM" )==0)
					gcs.auth_method = (int)AUTHENTICATE_PAM;
				else if (strcmp( value, "LDAP" )==0)
					gcs.auth_method = (int)AUTHENTICATE_LDAP;
				else if (strcmp( value, "Internal" )==0)
					gcs.auth_method = (int)AUTHENTICATE_LOCAL;
				else {
					printf( "Invalid authentication method, defaulting to internal\n" );
					gcs.auth_method = (int)AUTHENTICATE_LOCAL;
				};
			};
		};
	};

	// Now look for parameters for the method
	attrib = exnode->getAttributes();
	for (unsigned int i=0; i<attrib->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
		if (anode->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ATTRIBUTE_NODE) {
			UniTranslator name( anode->getNodeName() );
			UniTranslator value( anode->getNodeValue() );

			bool get_value = false;
			bool bad_value = false;

			// Check for service tag (PAM)
			if (strcmp( name, servicetag )==0) {
				if (gcs.auth_method == AUTHENTICATE_PAM)
					get_value = true;
				else
					bad_value = true;
			};

			// Check for server tag (LDAP)
			if (strcmp( name, servertag )==0) {
				if (gcs.auth_method == AUTHENTICATE_LDAP)
					get_value = true;
				else
					bad_value = true;
			};

			// We got a valid value that we need to save
			if (get_value)
				gcs.auth_parameter = strdup( value.UTF8() );

			if (bad_value)
				printf( "Invalid parameter for this method: %s\n", name.UTF8() );
		};
	};

	// No need to check children
};



void parms_reader::HandleSettings( XERCES_CPP_NAMESPACE::DOMNode* node, XCDebugInfo* currapp ) {
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator nname( node->getNodeName() );

			if (strcmp( nname.UTF8(), "AdminEmail" )==0) {
				XERCES_CPP_NAMESPACE::DOMNode* vnode = node->getFirstChild();
				UniTranslator value( vnode->getNodeValue() );
				currapp->admin_email = strdup( value );
			} else if (strcmp( nname.UTF8(), "SMTP" )==0) {
				XERCES_CPP_NAMESPACE::DOMNode* vnode = node->getFirstChild();
				UniTranslator value( vnode->getNodeValue() );
				currapp->smtp_host = strdup( value );
			} else if (strcmp( nname.UTF8(), "Printer" )==0) {
				XERCES_CPP_NAMESPACE::DOMNode* vnode = node->getFirstChild();
				UniTranslator value( vnode->getNodeValue() );
				currapp->printer = strdup( value );
			} else if (strcmp( nname.UTF8(), "FOPLocation" )==0) {
				XERCES_CPP_NAMESPACE::DOMNode* vnode = node->getFirstChild();
				UniTranslator value( vnode->getNodeValue() );
				currapp->fop_location = strdup( value );
			} else
				printf( "Invalid setting: %s\n", nname.UTF8() );
		};
		node = node->getNextSibling();
	};
};



void parms_reader::HandleProtea( XERCES_CPP_NAMESPACE::DOMNode* exnode ) {
	// Start parsing
	XERCES_CPP_NAMESPACE::DOMNode* node = exnode->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator name( node->getNodeName() );

			// Check for GCS tag
			if (strcmp( name, gcstag )==0) {
				XERCES_CPP_NAMESPACE::DOMNamedNodeMap* attrib = node->getAttributes();
				for (unsigned int i=0; i<attrib->getLength(); i++) {
					XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
					if (anode->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ATTRIBUTE_NODE) {
						UniTranslator name( anode->getNodeName() );
						UniTranslator value( anode->getNodeValue() );
						if (strcmp( name, porttag )==0 ) {
							gcs.port = atoi( value );
						} else if (strcmp( name, machinetag )==0 ) {
							gcs.machine = strdup( value );
						} else if (strcmp( name, threadstag )==0) {
							gcs.worker_threads = atoi( value );
						} else
							printf( "Invalid parameter on gcs tag: %s\n", name.UTF8() );
					};
				};

				// Check for debug lines...
				CheckDebugTags( node->getFirstChild(), &gcs );
			} else if (strcmp( name, applisttag )==0) {
				HandleAppList( node->getFirstChild() );
			} else if (strcmp( name, interfacelisttag )==0) {
				HandleInterfaceList( node->getFirstChild() );
			} else {
				printf( "Invalid XML format: %s\n", name.UTF8() );
			};
		};
		node = node->getNextSibling();
	};
};




bool parms_reader::LoadParms( const char* program_name ) {
	// If supplied a program name, then navigate to that directory
	if (program_name) {
		// Change to the path where the file is run from
		const char* temp = program_name + strlen( program_name );
		while (*temp != '\\' && temp != program_name)
			temp--;

		if (temp != program_name) {
			char* path = strdup( program_name );
			*(path + (temp - program_name)) = 0;
			XCChDir( path );
			free( path );
		};
	};

	// get startup parameters from gcs.xml configuration file
	char tempfile[ MAX_PATH ];
	sprintf( tempfile, "gcs.xml" );
	XCRegistry registry;
	char* root = NULL;
	if ( !XCFileExists( tempfile ) )
	{
		try {
			root = registry.GetStringValue( "XSIROOT" );
		} catch( XCToolsError& error ) {
			// On error, set the root to NULL
			root = NULL;
		};
	};

	if (root) {
		sprintf( tempfile, "%s%sTools%sgcs.xml", root, OSFILEDELIM, OSFILEDELIM );
		free( root );
	}

	// Parse XML
	XERCES_CPP_NAMESPACE::DOMDocument* doc;
	XERCES_CPP_NAMESPACE::XercesDOMParser *parser = new XERCES_CPP_NAMESPACE::XercesDOMParser;
	DOMTreeConsoleErrorReporter *errReporter = new DOMTreeConsoleErrorReporter();
	parser->setErrorHandler( errReporter );
	bool result = true;

	try {
		parser->setIncludeIgnorableWhitespace( false );
		parser->parse( tempfile );
		doc = parser->getDocument();
		assert( doc );

		if (!errReporter->getSawErrors()) {
			XERCES_CPP_NAMESPACE::DOMNode* node = doc->getDocumentElement();
			while (node) {
				UniTranslator name( node->getNodeName() );
	//			printf( "PRint node\n" );
	//			printf( "NODE: %s\n", name.UTF8() );
	//			assert( name.UTF8() );
				if (strcmp(name, proteatag)==0)
					HandleProtea( node );
				else
					printf( "Bad XML format: %s\n", name.UTF8() );

				node = node->getNextSibling();
			};
		} else {
			cout << "Error reading parms!" << endl;
			result = false;
		};
	} catch( const XERCES_CPP_NAMESPACE::XMLException& e ) {
		cerr << "An error occurred during parsing\n  Message: " << UniTranslator( e.getMessage() ).UTF8() << endl;
		result = false;
	} catch( const XERCES_CPP_NAMESPACE::DOMException& e ) {
		cerr << "A DOM error occurred during parsing\n  DOMException code: " << e.code << endl;
		result = false;
	};
	delete parser;

	return result;
};


parms_reader::parms_reader() {
	want_interfaces = true;
	want_apps = true;

	applist = NULL;
	interfacelist = NULL;
	gcs.machine = NULL;

	///
	// Set Defaults
	///

	// Default to internal authentication
	gcs.auth_method = (int)AUTHENTICATE_LOCAL;
	gcs.auth_parameter = NULL;

	// Default to single threaded
	gcs.worker_threads = 1;

	// Default to localhost as SMTP host
	gcs.smtp_host = NULL;

	// Default printer to NULL
	gcs.printer = NULL;
};


parms_reader::~parms_reader() {
	if (gcs.machine)
		free( gcs.machine );

	XCStartApp* tempapp;
	while ( applist )
	{
		tempapp = applist;
		applist = applist->next;
		delete tempapp;
	};

	XCStartInterface* tempint;
	while ( interfacelist )
	{
		tempint = interfacelist;
		interfacelist = interfacelist->next;
		delete tempint;
	};
};













//
// XCStartApp Class
//
XCStartApp::XCStartApp()
{
	number = 0;
	name = NULL;
	machine = NULL;
	NETapp = false;
	next = NULL;

	debug_type_filter = 0;
	for (int i=0; i<num_tc; i++)
		debug_level_cutoff[ i ] = 0;
};

XCStartApp::~XCStartApp()
{
	if ( name ) delete[] name;
	if ( machine ) delete[] machine;
};

//
// XCStartInterface Class
//
XCStartInterface::XCStartInterface()
{
	name = NULL;
	machine =NULL;
	port = 0;
	next = NULL;
};

XCStartInterface::~XCStartInterface()
{
	if ( name ) delete[] name;
	if ( machine ) delete[] machine;
};









long XCGetXMLPort() {
	if (XCFileExists( "gcs.xml" )) {
		char host[ 255 ];
		XCGetHostName( host, 255 );

		parms_reader reader;
		reader.want_apps = false;
		reader.want_interfaces = true;
		reader.LoadParms();

		XCStartInterface* temp = reader.interfacelist;
		while (temp) {
			if (strcmp( "xmlint", temp->name )==0 && strcmp( host, temp->machine)==0)
				return temp->port;

			temp = temp->next;
		};
	};
	
	// Some kind of error occurred
	return 0;
};
